package com.termux.project;
import android.os.*;
import java.util.*;
import java.io.*;
import android.util.*;
import java.lang.reflect.*;
import com.termux.ide.MainActivity;
import com.termux.mvp.*;
import java.nio.charset.*;
import com.termux.util.*;
import com.qmuiteam.qmui.widget.dialog.*;
import com.termux.app.TermuxService;

public class CompileTask extends BasePresenter<MainActivity>
{
	public static final String FILES_PATH = TermuxService.FILES_PATH;
    public static final String PREFIX_PATH = FILES_PATH + "/usr";
    public static final String HOME_PATH = FILES_PATH + "/home";
	private static final int COMPILE_HAND_CODE = 0x34;
	static int msDelay = 300;
    private static final String LOG_TAG = "termux-task";
	Runnable compileThread;
	public void toShowInfo(String msg)
	{
	final QMUITipDialog dialog = TipHelper.showError(getView(), msg);
			new Thread(){
				@Override
				public void run(){
					try
					{
						Thread.sleep(400);
						dialog.dismiss();
					}
					catch (InterruptedException e)
					{}
				}
			}.start();
	}
	Handler myHandler= new Handler(){

		@Override
		public void handleMessage(Message msg)
		{
			if (msg.what == COMPILE_HAND_CODE)
			{
				Bundle bundle = (Bundle)msg.obj;
				String result = String.valueOf(bundle.getString("stdout"));
				String err = String.valueOf(bundle.getString("stderr"));
				toShowInfo(result);
				if (!err.isEmpty())
				TipHelper.showError(getView(), err);
			}
		}

	};

	String cwd,processDescription;
	String[] progArray;
    public CompileTask(String cwd, String fileToExecute, final String[] args)
	{

        if (cwd == null) cwd = HOME_PATH;
		this.cwd = cwd;
        this.progArray = setupProcessArgs(fileToExecute, args);

        this.processDescription = Arrays.toString(progArray);
    }
	public int exec()
	{
		String[] env = buildEnvironment(false, cwd);
		java.lang.Process process;
        try
		{
            process = Runtime.getRuntime().exec(progArray, env, new File(cwd));
        }
		catch (IOException e)
		{
            process = null;
            // TODO: Visible error message?
            Log.d(LOG_TAG, "Failed running background job: " + processDescription, e);
			TipHelper.showError(getView(),e.getMessage());
            return -1;
        }
		final java.lang.Process mProcess = process;
        final int pid = getPid(process);
        final Bundle result = new Bundle();
        final StringBuilder outResult = new StringBuilder();
        final StringBuilder errResult = new StringBuilder();

        final Thread errThread = new Thread() {
            @Override
            public void run()
			{
                InputStream stderr = mProcess.getErrorStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(stderr, StandardCharsets.UTF_8));
                String line;
                try
				{
                    // FIXME: Long lines.
                    while ((line = reader.readLine()) != null)
					{
                        errResult.append(line).append('\n');
                        Log.i(LOG_TAG, "[" + pid + "] stderr: " + line);
                    }
                }
				catch (IOException e)
				{
                    // Ignore.
                }
            }
        };
        errThread.start();
        new Thread() {
            @Override
            public void run()
			{
                Log.i(LOG_TAG, "[" + pid + "] starting: " + processDescription);
                InputStream stdout = mProcess.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8));

                String line;
                try
				{
                    // FIXME: Long lines.
                    while ((line = reader.readLine()) != null)
					{
                        Log.i(LOG_TAG, "[" + pid + "] stdout: " + line);
                        outResult.append(line).append('\n');
                    }
                }
				catch (IOException e)
				{
                    Log.e(LOG_TAG, "Error reading output", e);
                }

                try
				{
                    result.putString("stdout", outResult.toString());
                    errThread.join();
                    result.putString("stderr", errResult.toString());
					Message message = new Message();
					message.what = COMPILE_HAND_CODE;
					message.obj = result;
					myHandler.sendMessage(message);


                }
				catch (InterruptedException e)
				{
                    // Ignore
                }
            }
        }.start();
		int exitCode = 0;
		try
		{
		    exitCode = mProcess.waitFor();
			if (exitCode == 0)
			{
				Log.i(LOG_TAG, "[" + pid + "] exited normally");
			}
			else
			{
				Log.w(LOG_TAG, "[" + pid + "] exited with code: " + exitCode);
			}
			return exitCode;
		}
		catch (InterruptedException e)
		{

		}
		return exitCode;
	}
    private static void addToEnvIfPresent(List<String> environment, String name)
	{
        String value = System.getenv(name);
        if (value != null)
		{
            environment.add(name + "=" + value);
        }
    }

    static String[] buildEnvironment(boolean failSafe, String cwd)
	{
        new File(HOME_PATH).mkdirs();

        if (cwd == null) cwd = HOME_PATH;

        List<String> environment = new ArrayList<>();

        environment.add("TERM=xterm-256color");
        environment.add("HOME=" + HOME_PATH);
        environment.add("PREFIX=" + PREFIX_PATH);
        environment.add("BOOTCLASSPATH=" + System.getenv("BOOTCLASSPATH"));
        environment.add("ANDROID_ROOT=" + System.getenv("ANDROID_ROOT"));
        environment.add("ANDROID_DATA=" + System.getenv("ANDROID_DATA"));
        // EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
        // Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
        environment.add("EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE"));
        // ANDROID_RUNTIME_ROOT and ANDROID_TZDATA_ROOT are required for `am` to run on Android Q
        addToEnvIfPresent(environment, "ANDROID_RUNTIME_ROOT");
        addToEnvIfPresent(environment, "ANDROID_TZDATA_ROOT");
        if (failSafe)
		{
            // Keep the default path so that system binaries can be used in the failsafe session.
            environment.add("PATH= " + System.getenv("PATH"));
        }
		else
		{
            if (shouldAddLdLibraryPath())
			{
                environment.add("LD_LIBRARY_PATH=" + PREFIX_PATH + "/lib");
            }
            environment.add("LANG=en_US.UTF-8");
            environment.add("PATH=" + PREFIX_PATH + "/bin:" + PREFIX_PATH + "/bin/applets");
            environment.add("PWD=" + cwd);
            environment.add("TMPDIR=" + PREFIX_PATH + "/tmp");
        }

        return environment.toArray(new String[0]);
    }

    private static boolean shouldAddLdLibraryPath()
	{
        try
		{
			BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(PREFIX_PATH + "/etc/apt/sources.list")));
            String line;
            while ((line = in.readLine()) != null)
			{
                if (!line.startsWith("#") && line.contains("//termux.net stable"))
				{
                    return true;
                }
            }
        }
		catch (IOException e)
		{
            Log.e(LOG_TAG, "Error trying to read sources.list", e);
        }
        return false;
    }

    public static int getPid(java.lang.Process p)
	{
        try
		{
            Field f = p.getClass().getDeclaredField("pid");
            f.setAccessible(true);
            try
			{
                return f.getInt(p);
            }
			finally
			{
                f.setAccessible(false);
            }
        }
		catch (Throwable e)
		{
            return -1;
        }
    }

    static String[] setupProcessArgs(String fileToExecute, String[] args)
	{
        // The file to execute may either be:
        // - An elf file, in which we execute it directly.
        // - A script file without shebang, which we execute with our standard shell $PREFIX/bin/sh instead of the
        //   system /system/bin/sh. The system shell may vary and may not work at all due to LD_LIBRARY_PATH.
        // - A file with shebang, which we try to handle with e.g. /bin/foo -> $PREFIX/bin/foo.
        String interpreter = null;
        try
		{
            File file = new File(fileToExecute);
            try (FileInputStream in = new FileInputStream(file)) {
                byte[] buffer = new byte[256];
                int bytesRead = in.read(buffer);
                if (bytesRead > 4)
				{
                    if (buffer[0] == 0x7F && buffer[1] == 'E' && buffer[2] == 'L' && buffer[3] == 'F')
					{
                        // Elf file, do nothing.
                    }
					else if (buffer[0] == '#' && buffer[1] == '!')
					{
                        // Try to parse shebang.
                        StringBuilder builder = new StringBuilder();
                        for (int i = 2; i < bytesRead; i++)
						{
                            char c = (char) buffer[i];
                            if (c == ' ' || c == '\n')
							{
                                if (builder.length() == 0)
								{
                                    // Skip whitespace after shebang.
                                }
								else
								{
                                    // End of shebang.
                                    String executable = builder.toString();
                                    if (executable.startsWith("/usr") || executable.startsWith("/bin"))
									{
                                        String[] parts = executable.split("/");
                                        String binary = parts[parts.length - 1];
                                        interpreter = PREFIX_PATH + "/bin/" + binary;
                                    }
                                    break;
                                }
                            }
							else
							{
                                builder.append(c);
                            }
                        }
                    }
					else
					{
                        // No shebang and no ELF, use standard shell.
                        interpreter = PREFIX_PATH + "/bin/sh";
                    }
                }
            }
        }
		catch (IOException e)
		{
            // Ignore.
        }

        List<String> result = new ArrayList<>();
        if (interpreter != null) result.add(interpreter);
        result.add(fileToExecute);
        if (args != null) Collections.addAll(result, args);
        return result.toArray(new String[0]);
    }

}
